﻿namespace SharpF2.Services
{
	using System;
	using System.ComponentModel;
	using System.Globalization;
	using System.IO;
	using System.Net;
	using System.Reflection;
	using System.Runtime.Serialization;
	using System.Runtime.Serialization.Formatters.Soap;
	using System.Runtime.Serialization.Json;
	using System.Text;
	using System.Text.RegularExpressions;
	using System.Threading;
	using System.Web;
	using System.Windows;
	using ModelView;
	using Properties;
	using View;

	public class WebServices : INotifyPropertyChanged
	{
		#region Events

		public event PropertyChangedEventHandler PropertyChanged;

		#endregion

		#region Constructors

		private WebServices()
		{
			CurrentVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
			NewVersionAvailable = false;

			OnPropertyChanged("NewVersionAvailable");

			GetLatestVersion();
		}

		#endregion

		#region Private Attributes

		private static readonly WebServices instance = new WebServices();

		#endregion

		#region Public Properties

		public static WebServices Instance
		{
			get { return instance; }
		}

		public String CurrentVersion { get; private set; }
		public Boolean NewVersionAvailable { get; private set; }

		public String OnlineVersion { get; private set; }

		public string UpdateURI { get; private set; }

		#endregion

		#region Public Members

		/// <summary>
		/// Check for the new version number on internet.
		/// </summary>
		public void GetLatestVersion()
		{
			var getLastVersion = new GetLastVersion();
			AsyncSendRequest("GetLastVersion", getLastVersion, o =>
			                                                   	{
			                                                   		if (String.IsNullOrEmpty(o.OnlineVersion) ||
			                                                   		    String.IsNullOrEmpty(o.UpdateURI)) return;
			                                                   		if (CurrentVersion.Equals(o.OnlineVersion)) return;
			                                                   		OnlineVersion = o.OnlineVersion;
			                                                   		UpdateURI = o.UpdateURI;
			                                                   		NewVersionAvailable = true;
			                                                   		OnPropertyChanged("NewVersionAvailable");
			                                                   	});
		}

		/// <summary>
		/// Report an error to support email.
		/// </summary>
		/// <param name="e">Exception parameter.</param>
		public void ReportException(Exception e)
		{
			try
			{
				var serializedException = SerializeException(e);
				var exceptionToSend = HttpUtility.UrlEncode(serializedException);

				if (String.IsNullOrEmpty(exceptionToSend)) return;

				var exceptionReport = new ExceptionReport
				                      	{
				                      		Version = Assembly.GetExecutingAssembly().GetName().Version.ToString(),
				                      		Exception = exceptionToSend
				                      	};

				var dialogModelView = new ReportErrorDialogModelView(serializedException);
				var dialog = new ReportErrorDialog {DataContext = dialogModelView};
				dialog.ShowDialog();

				if (!dialogModelView.SendError) return;

				var url = new StringBuilder("http://sharpf2.sourceforge.net/Prod.php");
				url.Append("?action=" + "ExceptionReport");

				try
				{
					// Create a HttpWebrequest object to the desired URL. 
					var myHttpWebRequest = (HttpWebRequest) WebRequest.Create(url.ToString());

					myHttpWebRequest.ContentType = "application/x-www-form-urlencoded";
					myHttpWebRequest.Method = "POST";
					var reqBytes =
						Encoding.ASCII.GetBytes("Version=" + exceptionReport.Version + "&Exception=" + exceptionReport.Exception);
					myHttpWebRequest.ContentLength = reqBytes.Length;
					var oReqStream = myHttpWebRequest.GetRequestStream();
					oReqStream.Write(reqBytes, 0, reqBytes.Length);
					oReqStream.Flush();
					oReqStream.Close();

					// Start the asynchronous request.
					myHttpWebRequest.GetResponse();
				}
				catch (Exception ex)
				{
					UserMessageBox(ex);
				}
			}
			catch (Exception ex)
			{
				UserMessageBox(ex);
			}
		}

		/// <summary>
		/// Report the comments to SharpF2 developpers.
		/// </summary>
		/// <param name="message">The message of the user</param>
		public void ReportUserComment(String message)
		{
			try
			{
				var userComment = new UserComment {Message = message};
				SendRequest("AddUserCommentReport", ref userComment);

				if ("Succeed".Equals(userComment.Response))
					MessageBox.Show(Resources.UserCommentReceived);
			}
			catch (Exception exception)
			{
				UserMessageBox(exception);
			}
		}

		#endregion

		#region Private Members

		private static void AsyncSendRequest<ItemType>(String action, ItemType item, Action<ItemType> callback)
		{
			var parameters = new StringBuilder();
			var json = new DataContractJsonSerializer(typeof (ItemType));
			using (var ms = new MemoryStream())
			{
				json.WriteObject(ms, item);
				var s = Encoding.Default.GetString(ms.ToArray());
				parameters.Append(s);
			}

			var url = new StringBuilder("http://sharpf2.sourceforge.net/Prod.php");
			url.Append("?action=" + action);
			url.Append("&parameters=" + parameters);

			try
			{
				// Create a HttpWebrequest object to the desired URL. 
				var myHttpWebRequest = (HttpWebRequest) WebRequest.Create(url.ToString());

				// Create an instance of the RequestState and assign the previous myHttpWebRequest
				// object to its request field.  
				var myRequestState = new RequestState<ItemType> {request = myHttpWebRequest, Item = item};

				// Start the asynchronous request.
				var result = myHttpWebRequest.BeginGetResponse(o => callback(Callback<ItemType>(o)), myRequestState);
				// this line implements the timeout, if there is a timeout, the callback fires and the request becomes aborted
				ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, TimeoutCallback, myHttpWebRequest, DefaultTimeout,
				                                       true);
			}
			catch (Exception exception)
			{
				LoggingService.Instance.WriteToLog(exception.Message);
			}
		}

		private static ItemType Callback<ItemType>(IAsyncResult asynchronousResult)
		{
			var myRequestState = (RequestState<ItemType>) asynchronousResult.AsyncState;

			try
			{
				// State of request is asynchronous.
				var myHttpWebRequest = myRequestState.request;

				// Read the response into a Stream object.
				var responseStream = myHttpWebRequest.EndGetResponse(asynchronousResult).GetResponseStream();

				var json = new DataContractJsonSerializer(typeof (ItemType));
				using (var ms = responseStream)
				{
					myRequestState.Item = (ItemType) json.ReadObject(ms);
				}
			}
			catch (WebException exception)
			{
				LoggingService.Instance.WriteToLog(exception.Message);
			}

			return myRequestState.Item;
		}

		private const int DefaultTimeout = 2 * 60 * 1000;

		private static void SendRequest<ItemType>(String action, ref ItemType item)
		{
			var parameters = new StringBuilder();
			var json = new DataContractJsonSerializer(typeof (ItemType));
			using (var ms = new MemoryStream())
			{
				json.WriteObject(ms, item);
				var s = Encoding.Default.GetString(ms.ToArray());
				parameters.Append(s);
			}

			var url = new StringBuilder("http://sharpf2.sourceforge.net/Prod.php");
			url.Append("?action=" + action);
			url.Append("&parameters=" + parameters);

			try
			{
				// Create a HttpWebrequest object to the desired URL. 
				var myHttpWebRequest = (HttpWebRequest) WebRequest.Create(url.ToString());

				// Start the asynchronous request.
				var result = myHttpWebRequest.GetResponse();

				item = (ItemType) json.ReadObject(result.GetResponseStream());
			}
			catch (Exception e)
			{
				UserMessageBox(e);
			}
		}

		private static String SerializeException(Exception exception)
		{
			var serializedException = "";

			var t = new Thread(() =>
			                   	{
			                   		using (var ms = new MemoryStream(32))
			                   		{
			                   			var formatter = new SoapFormatter();
			                   			formatter.Serialize(ms, exception);
			                   			ms.Position = 0;
			                   			using (var sr = new StreamReader(ms))
			                   			{
			                   				serializedException = sr.ReadToEnd();
			                   			}
			                   		}
			                   		var regex = new Regex(" in .:.*SharpF2");
			                   		var match = regex.Match(serializedException);
			                   		if (match.Success)
			                   			serializedException = serializedException.Replace(match.ToString(), "in ...\\SharpF2");
			                   	}
				)
			        	{
			        		CurrentUICulture = new CultureInfo("en-US")
			        	};
			t.Start();
			t.Join();


			return serializedException;
		}

		private static void TimeoutCallback(object state, bool timedOut)
		{
			if (!timedOut) return;
			var request = state as HttpWebRequest;
			if (request != null)
			{
				request.Abort();
			}
		}

		private static void UserMessageBox(Exception exception)
		{
			LoggingService.Instance.WriteToLog(exception.Message);
			MessageBox.Show(exception.Message);
		}

		private void OnPropertyChanged(String propertyName)
		{
			if (null != PropertyChanged)
				PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
		}

		#endregion

		#region Nested type: ExceptionReport

		[DataContract]
		private class ExceptionReport
		{
			#region Public Properties

			[DataMember]
			public String Exception { get; set; }

			[DataMember]
			public String Response { get; set; }

			[DataMember]
			public String Version { get; set; }

			#endregion
		}

		#endregion

		#region Nested type: GetLastVersion

		[DataContract]
		private class GetLastVersion
		{
			#region Public Properties

			[DataMember]
			public String OnlineVersion { get; set; }

			[DataMember]
			public String UpdateURI { get; set; }

			#endregion
		}

		#endregion

		#region Nested type: RequestState

		private class RequestState<ItemType>
		{
			// This class stores the State of the request.

			#region Public Attributes

			public HttpWebRequest request;

			#endregion

			#region Public Properties

			public ItemType Item { get; set; }

			#endregion
		}

		#endregion

		#region Nested type: UserComment

		[DataContract]
		private class UserComment
		{
			#region Public Properties

			[DataMember]
			public String Message { get; set; }

			[DataMember]
			public String Response { get; set; }

			#endregion
		}

		#endregion
	}
}